package com.learning.spring.aop.aspectj.pointcut.expression.function;

import com.learning.spring.aop.aspectj.pointcut.expression.annotation.LogEntity;
import com.learning.spring.aop.aspectj.pointcut.expression.entity.User;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;

/**
 * ClassName: AdvancePointcutFunction
 * Description: 进阶用法
 * Date: 2020/10/15 10:25 【需求编号】
 *
 * @author Sam Sho
 * @version V1.0.0
 */
@Aspect
public class AdvancePointcutFunction {

    /**
     * 切点复合运算：使用逻辑运算进行复合运算
     * 与运算
     * 或运算（||）
     * 非与运算（!&&）
     */
    @Before("within(com.learning.spring.aop.aspectj..*)" + "&& execution(* add(..))")
    public void before() {
        System.out.println("Hello PointcutFunction 复合运算");
    }

    /**
     * 1. 命名切点的最大作用是定义的切点可以重用
     * 2. 使用 @Pointcut 注解和切面类方法定义切点，然后在函数入参处直接传入这个切面类方法
     */
    @Pointcut("execution(* update(..))")
    public void pointcut() {
    }

    /**
     * 定义切点，然后可以重用
     */
    @AfterReturning("pointcut()")
    public void after() {
        System.out.println("Hello @AfterReturning  @Pointcut 命名切点");
    }

    /**
     * 1. AspectJ 使用 org.aspectj.lang.JoinPoint 接口表示目标类连接点对象
     *
     * @param joinPoint
     */
    @Before("execution(* add(..))")
    public void joinPoint(JoinPoint joinPoint) {

        // 获取连接点方法运行时的入参列表
        Object[] args = joinPoint.getArgs();


        // 获取连接点的方法签名对象
        Signature signature = joinPoint.getSignature();

        // 获取连接点所在的目标对象
        Object target = joinPoint.getTarget();

        // 获取代理对象本身
        Object aThis = joinPoint.getThis();
    }

    /**
     * 1. 如果是环绕 Advice，则使用 org.aspectj.lang.ProceedingJoinPoint 表示连接点对象。
     * 1.1 joinPoint.proceed();：通过反射执行目标对象的连接点处方法
     * 1.2 joinPoint.proceed(args);：执行目标对象的连接点处方法，使用新的参数
     *
     * @param joinPoint
     * @throws Throwable
     */
    @Around("execution(* add(..))")
    public void joinPoint(ProceedingJoinPoint joinPoint) throws Throwable {

        // 获取连接点方法运行时的入参列表
        Object[] args = joinPoint.getArgs();


        // 获取连接点的方法签名对象
        Signature signature = joinPoint.getSignature();

        // 获取连接点所在的目标对象
        Object target = joinPoint.getTarget();

        // 获取代理对象本身
        Object aThis = joinPoint.getThis();

        // 通过反射执行目标对象的连接点处方法
        joinPoint.proceed();

        // 执行目标对象的连接点处方法，使用新的参数
        args[0] = "Eason";
        joinPoint.proceed(args);
    }

    /**
     * 绑定连接点方法入参:
     * 1. 像args、this、target、@args、@winthin、@target、@annotation 这 7 个函数不仅可以指定类名外，还可以指定参数名，将目标对象连接点的方法入参绑定到Advice增强的方法中。其实也可以使用连接点信息类 JoinPoint 获取
     * 2. 比如 args 用于绑定连接点方法的入参；@annotation 用于绑定连接点方法的类注解对象；@args 用于绑定连接点方法入参的注解
     * 3. 下面的例子：匹配 AopService 类中方法入参为User的方法。args(参数名)：
     * 3.1 连接点匹配规则：连接点方法的入参是 User 类型
     * 3.2 连接点方法入参和增强方法入参绑定
     */
    @Before("target(com.learning.spring.aop.aspectj.pointcut.expression.service.impl.AopServiceImpl)" + "&& args(user)")
    public void args(User user) {
        System.out.println("Hello @Before args 绑定连接点方法入参");
        System.out.println(user);
    }

    /**
     * 绑定类注解对象
     * 1. @winthin、@target 函数可以将目标类的注解对象绑定到 Advice 增强方法中
     * 2. 例子：
     * 使用 @winthin 函数绑定注解
     *
     * @param logEntity
     */
    @Before("@within(logEntity)")
    public void annotation(LogEntity logEntity) {
        System.out.println("Hello @within annotation args 绑定类注解对象");
        System.out.println(logEntity);
    }

    /**
     * 绑定返回值
     * <p>
     * 在后置 Advice 增强中，可以通过 returning 属性绑定连接点方法的返回值
     *
     * @param result
     */
    @AfterReturning(value = "execution(* execute(..))", returning = "result")
    public void afterReturningValue(String result) {
        System.out.println("Hello @AfterReturning returning value 绑定返回值");
        System.out.println(result);
    }

    /**
     * 绑定异常
     * <p>
     * 连接点抛出异常必须使用 @AfterThrowing 注解的 throwing 属性进行绑定
     *
     * @param ex
     */
    @AfterThrowing(value = "execution(* exception(..))", throwing = "ex")
    public void afterThrowing(Exception ex) {
        System.out.println("Hello @AfterReturning returning value 绑定异常");
        System.out.println(ex);
    }
}
